// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/browser_thread_impl.h"

#include <string>

#include "base/atomicops.h"
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/profiler/scoped_tracker.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "base/threading/sequenced_worker_pool.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "net/disk_cache/simple/simple_backend_impl.h"

#if defined(OS_ANDROID)
#include "base/android/jni_android.h"
#endif

namespace content {

namespace {

    // Friendly names for the well-known threads.
    static const char* const g_browser_thread_names[BrowserThread::ID_COUNT] = {
        "", // UI (name assembled in browser_main.cc).
        "Chrome_DBThread", // DB
        "Chrome_FileThread", // FILE
        "Chrome_FileUserBlockingThread", // FILE_USER_BLOCKING
        "Chrome_ProcessLauncherThread", // PROCESS_LAUNCHER
        "Chrome_CacheThread", // CACHE
        "Chrome_IOThread", // IO
    };

    static const char* GetThreadName(BrowserThread::ID thread)
    {
        if (BrowserThread::UI < thread && thread < BrowserThread::ID_COUNT)
            return g_browser_thread_names[thread];
        if (thread == BrowserThread::UI)
            return "Chrome_UIThread";
        return "Unknown Thread";
    }

    // An implementation of SingleThreadTaskRunner to be used in conjunction
    // with BrowserThread.
    // TODO(gab): Consider replacing this with |g_globals->task_runners| -- only
    // works if none are requested before starting the threads.
    class BrowserThreadTaskRunner : public base::SingleThreadTaskRunner {
    public:
        explicit BrowserThreadTaskRunner(BrowserThread::ID identifier)
            : id_(identifier)
        {
        }

        // SingleThreadTaskRunner implementation.
        bool PostDelayedTask(const tracked_objects::Location& from_here,
            const base::Closure& task,
            base::TimeDelta delay) override
        {
            return BrowserThread::PostDelayedTask(id_, from_here, task, delay);
        }

        bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
            const base::Closure& task,
            base::TimeDelta delay) override
        {
            return BrowserThread::PostNonNestableDelayedTask(id_, from_here, task,
                delay);
        }

        bool RunsTasksOnCurrentThread() const override
        {
            return BrowserThread::CurrentlyOn(id_);
        }

    protected:
        ~BrowserThreadTaskRunner() override { }

    private:
        BrowserThread::ID id_;
        DISALLOW_COPY_AND_ASSIGN(BrowserThreadTaskRunner);
    };

    // A separate helper is used just for the task runners, in order to avoid
    // needing to initialize the globals to create a task runner.
    struct BrowserThreadTaskRunners {
        BrowserThreadTaskRunners()
        {
            for (int i = 0; i < BrowserThread::ID_COUNT; ++i) {
                proxies[i] = new BrowserThreadTaskRunner(static_cast<BrowserThread::ID>(i));
            }
        }

        scoped_refptr<base::SingleThreadTaskRunner> proxies[BrowserThread::ID_COUNT];
    };

    base::LazyInstance<BrowserThreadTaskRunners>::Leaky g_task_runners = LAZY_INSTANCE_INITIALIZER;

    // State of a given BrowserThread::ID in chronological order throughout the
    // browser process' lifetime.
    enum BrowserThreadState {
        // BrowserThread::ID isn't associated with anything yet.
        UNINITIALIZED = 0,
        // BrowserThread::ID is associated with a BrowserThreadImpl instance but the
        // underlying thread hasn't started yet.
        INITIALIZED,
        // BrowserThread::ID is associated to a TaskRunner and is accepting tasks.
        RUNNING,
        // BrowserThread::ID no longer accepts tasks.
        SHUTDOWN
    };

    using BrowserThreadDelegateAtomicPtr = base::subtle::AtomicWord;

    struct BrowserThreadGlobals {
        BrowserThreadGlobals()
            : blocking_pool(
                new base::SequencedWorkerPool(3,
                    "BrowserBlocking",
                    base::TaskPriority::USER_VISIBLE))
        {
        }

        // This lock protects |task_runners| and |states|. Do not read or modify those
        // arrays without holding this lock. Do not block while holding this lock.
        base::Lock lock;

        // This array is filled either as the underlying threads start and invoke
        // Init() or in RedirectThreadIDToTaskRunner() for threads that are being
        // redirected. It is not emptied during shutdown in order to support
        // RunsTasksOnCurrentThread() until the very end.
        scoped_refptr<base::SingleThreadTaskRunner>
            task_runners[BrowserThread::ID_COUNT];

        // Holds the state of each BrowserThread::ID.
        BrowserThreadState states[BrowserThread::ID_COUNT] = {};

        // Only atomic operations are used on this pointer. The delegate isn't owned
        // by BrowserThreadGlobals, rather by whoever calls
        // BrowserThread::SetIOThreadDelegate.
        BrowserThreadDelegateAtomicPtr io_thread_delegate = 0;

        const scoped_refptr<base::SequencedWorkerPool> blocking_pool;
    };

    base::LazyInstance<BrowserThreadGlobals>::Leaky
        g_globals
        = LAZY_INSTANCE_INITIALIZER;

} // namespace

BrowserThreadImpl::BrowserThreadImpl(ID identifier)
    : Thread(GetThreadName(identifier))
    , identifier_(identifier)
{
    Initialize();
}

BrowserThreadImpl::BrowserThreadImpl(ID identifier,
    base::MessageLoop* message_loop)
    : Thread(GetThreadName(identifier))
    , identifier_(identifier)
{
    SetMessageLoop(message_loop);
    Initialize();

    // If constructed with an explicit message loop, this is a fake
    // BrowserThread which runs on the current thread.
    BrowserThreadGlobals& globals = g_globals.Get();
    base::AutoLock lock(globals.lock);

    DCHECK(!globals.task_runners[identifier_]);
    globals.task_runners[identifier_] = task_runner();

    DCHECK_EQ(globals.states[identifier_], BrowserThreadState::INITIALIZED);
    globals.states[identifier_] = BrowserThreadState::RUNNING;
}

// static
void BrowserThreadImpl::ShutdownThreadPool()
{
    // The goal is to make it impossible for chrome to 'infinite loop' during
    // shutdown, but to reasonably expect that all BLOCKING_SHUTDOWN tasks queued
    // during shutdown get run. There's nothing particularly scientific about the
    // number chosen.
    const int kMaxNewShutdownBlockingTasks = 1000;
    BrowserThreadGlobals& globals = g_globals.Get();
    globals.blocking_pool->Shutdown(kMaxNewShutdownBlockingTasks);
}

// static
void BrowserThreadImpl::FlushThreadPoolHelperForTesting()
{
    // We don't want to create a pool if none exists.
    if (g_globals == nullptr)
        return;
    g_globals.Get().blocking_pool->FlushForTesting();
    disk_cache::SimpleBackendImpl::FlushWorkerPoolForTesting();
}

void BrowserThreadImpl::Init()
{
    BrowserThreadGlobals& globals = g_globals.Get();

#if DCHECK_IS_ON()
    {
        base::AutoLock lock(globals.lock);
        // |globals| should already have been initialized for |identifier_| in
        // BrowserThreadImpl::StartWithOptions(). If this isn't the case it's likely
        // because this BrowserThreadImpl's owner incorrectly used Thread::Start.*()
        // instead of BrowserThreadImpl::Start.*().
        DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING);
        DCHECK(globals.task_runners[identifier_]);
        DCHECK(globals.task_runners[identifier_]->RunsTasksOnCurrentThread());
    }
#endif // DCHECK_IS_ON()

    if (identifier_ == BrowserThread::DB || identifier_ == BrowserThread::FILE || identifier_ == BrowserThread::FILE_USER_BLOCKING || identifier_ == BrowserThread::PROCESS_LAUNCHER || identifier_ == BrowserThread::CACHE) {
        // Nesting and task observers are not allowed on redirected threads.
        message_loop()->DisallowNesting();
        message_loop()->DisallowTaskObservers();
    }

    if (identifier_ == BrowserThread::IO) {
        BrowserThreadDelegateAtomicPtr delegate = base::subtle::NoBarrier_Load(&globals.io_thread_delegate);
        if (delegate)
            reinterpret_cast<BrowserThreadDelegate*>(delegate)->Init();
    }
}

// We disable optimizations for this block of functions so the compiler doesn't
// merge them all together.
MSVC_DISABLE_OPTIMIZE()
MSVC_PUSH_DISABLE_WARNING(4748)

NOINLINE void BrowserThreadImpl::UIThreadRun(base::RunLoop* run_loop)
{
    volatile int line_number = __LINE__;
    Thread::Run(run_loop);
    CHECK_GT(line_number, 0);
}

NOINLINE void BrowserThreadImpl::DBThreadRun(base::RunLoop* run_loop)
{
    volatile int line_number = __LINE__;
    Thread::Run(run_loop);
    CHECK_GT(line_number, 0);
}

NOINLINE void BrowserThreadImpl::FileThreadRun(base::RunLoop* run_loop)
{
    volatile int line_number = __LINE__;
    Thread::Run(run_loop);
    CHECK_GT(line_number, 0);
}

NOINLINE void BrowserThreadImpl::FileUserBlockingThreadRun(
    base::RunLoop* run_loop)
{
    volatile int line_number = __LINE__;
    Thread::Run(run_loop);
    CHECK_GT(line_number, 0);
}

NOINLINE void BrowserThreadImpl::ProcessLauncherThreadRun(
    base::RunLoop* run_loop)
{
    volatile int line_number = __LINE__;
    Thread::Run(run_loop);
    CHECK_GT(line_number, 0);
}

NOINLINE void BrowserThreadImpl::CacheThreadRun(base::RunLoop* run_loop)
{
    volatile int line_number = __LINE__;
    Thread::Run(run_loop);
    CHECK_GT(line_number, 0);
}

NOINLINE void BrowserThreadImpl::IOThreadRun(base::RunLoop* run_loop)
{
    volatile int line_number = __LINE__;
    Thread::Run(run_loop);
    CHECK_GT(line_number, 0);
}

MSVC_POP_WARNING()
MSVC_ENABLE_OPTIMIZE();

void BrowserThreadImpl::Run(base::RunLoop* run_loop)
{
#if defined(OS_ANDROID)
    // Not to reset thread name to "Thread-???" by VM, attach VM with thread name.
    // Though it may create unnecessary VM thread objects, keeping thread name
    // gives more benefit in debugging in the platform.
    if (!thread_name().empty()) {
        base::android::AttachCurrentThreadWithName(thread_name());
    }
#endif

    BrowserThread::ID thread_id = ID_COUNT;
    CHECK(GetCurrentThreadIdentifier(&thread_id));
    CHECK_EQ(identifier_, thread_id);

    switch (identifier_) {
    case BrowserThread::UI:
        return UIThreadRun(run_loop);
    case BrowserThread::DB:
        return DBThreadRun(run_loop);
    case BrowserThread::FILE:
        return FileThreadRun(run_loop);
    case BrowserThread::FILE_USER_BLOCKING:
        return FileUserBlockingThreadRun(run_loop);
    case BrowserThread::PROCESS_LAUNCHER:
        return ProcessLauncherThreadRun(run_loop);
    case BrowserThread::CACHE:
        return CacheThreadRun(run_loop);
    case BrowserThread::IO:
        return IOThreadRun(run_loop);
    case BrowserThread::ID_COUNT:
        CHECK(false); // This shouldn't actually be reached!
        break;
    }

    // |identifier_| must be set to a valid enum value in the constructor, so it
    // should be impossible to reach here.
    CHECK(false);
}

void BrowserThreadImpl::CleanUp()
{
    BrowserThreadGlobals& globals = g_globals.Get();

    if (identifier_ == BrowserThread::IO) {
        BrowserThreadDelegateAtomicPtr delegate = base::subtle::NoBarrier_Load(&globals.io_thread_delegate);
        if (delegate)
            reinterpret_cast<BrowserThreadDelegate*>(delegate)->CleanUp();
    }

    // Change the state to SHUTDOWN so that PostTaskHelper stops accepting tasks
    // for this thread. Do not clear globals.task_runners[identifier_] so that
    // BrowserThread::CurrentlyOn() works from the MessageLoop's
    // DestructionObservers.
    base::AutoLock lock(globals.lock);
    DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING);
    globals.states[identifier_] = BrowserThreadState::SHUTDOWN;
}

void BrowserThreadImpl::Initialize()
{
    BrowserThreadGlobals& globals = g_globals.Get();

    base::AutoLock lock(globals.lock);
    DCHECK_GE(identifier_, 0);
    DCHECK_LT(identifier_, ID_COUNT);
    DCHECK_EQ(globals.states[identifier_], BrowserThreadState::UNINITIALIZED);
    globals.states[identifier_] = BrowserThreadState::INITIALIZED;
}

// static
void BrowserThreadImpl::ResetGlobalsForTesting(BrowserThread::ID identifier)
{
    BrowserThreadGlobals& globals = g_globals.Get();

    base::AutoLock lock(globals.lock);
    DCHECK_EQ(globals.states[identifier], BrowserThreadState::SHUTDOWN);
    globals.states[identifier] = BrowserThreadState::UNINITIALIZED;
    globals.task_runners[identifier] = nullptr;
    if (identifier == BrowserThread::IO)
        SetIOThreadDelegate(nullptr);
}

BrowserThreadImpl::~BrowserThreadImpl()
{
    // All Thread subclasses must call Stop() in the destructor. This is
    // doubly important here as various bits of code check they are on
    // the right BrowserThread.
    Stop();

    BrowserThreadGlobals& globals = g_globals.Get();
    base::AutoLock lock(globals.lock);
    // This thread should have gone through Cleanup() as part of Stop() and be in
    // the SHUTDOWN state already (unless it uses an externally provided
    // MessageLoop instead of a real underlying thread and thus doesn't go through
    // Cleanup()).
    if (using_external_message_loop()) {
        DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING);
        globals.states[identifier_] = BrowserThreadState::SHUTDOWN;
    } else {
        DCHECK_EQ(globals.states[identifier_], BrowserThreadState::SHUTDOWN);
    }
#if DCHECK_IS_ON()
    // Double check that the threads are ordered correctly in the enumeration.
    for (int i = identifier_ + 1; i < ID_COUNT; ++i) {
        DCHECK(globals.states[i] == BrowserThreadState::SHUTDOWN || globals.states[i] == BrowserThreadState::UNINITIALIZED)
            << "Threads must be listed in the reverse order that they die";
    }
#endif
}

bool BrowserThreadImpl::Start()
{
    return StartWithOptions(base::Thread::Options());
}

bool BrowserThreadImpl::StartWithOptions(const Options& options)
{
    BrowserThreadGlobals& globals = g_globals.Get();

    // Holding the lock is necessary when kicking off the thread to ensure
    // |states| and |task_runners| are updated before it gets to query them.
    base::AutoLock lock(globals.lock);

    bool result = Thread::StartWithOptions(options);

    // Although the thread is starting asynchronously, the MessageLoop is already
    // ready to accept tasks and as such this BrowserThreadImpl is considered as
    // "running".
    DCHECK(!globals.task_runners[identifier_]);
    globals.task_runners[identifier_] = task_runner();
    DCHECK(globals.task_runners[identifier_]);

    DCHECK_EQ(globals.states[identifier_], BrowserThreadState::INITIALIZED);
    globals.states[identifier_] = BrowserThreadState::RUNNING;

    return result;
}

bool BrowserThreadImpl::StartAndWaitForTesting()
{
    if (!Start())
        return false;
    WaitUntilThreadStarted();
    return true;
}

// static
void BrowserThreadImpl::RedirectThreadIDToTaskRunner(
    BrowserThread::ID identifier,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
{
    DCHECK(task_runner);

    BrowserThreadGlobals& globals = g_globals.Get();
    base::AutoLock lock(globals.lock);

    DCHECK(!globals.task_runners[identifier]);
    DCHECK_EQ(globals.states[identifier], BrowserThreadState::UNINITIALIZED);

    globals.task_runners[identifier] = std::move(task_runner);
    globals.states[identifier] = BrowserThreadState::RUNNING;
}

// static
void BrowserThreadImpl::StopRedirectionOfThreadID(
    BrowserThread::ID identifier)
{
    BrowserThreadGlobals& globals = g_globals.Get();
    base::AutoLock auto_lock(globals.lock);

    DCHECK(globals.task_runners[identifier]);

    // Change the state to SHUTDOWN to stop accepting new tasks. Note: this is
    // different from non-redirected threads which continue accepting tasks while
    // being joined and only quit when idle. However, any tasks for which this
    // difference matters was already racy as any thread posting a task after the
    // Signal task below can't be synchronized with the joining thread. Therefore,
    // that task could already come in before or after the join had completed in
    // the non-redirection world. Entering SHUTDOWN early merely skews this race
    // towards making it less likely such a task is accepted by the joined thread
    // which is fine.
    DCHECK_EQ(globals.states[identifier], BrowserThreadState::RUNNING);
    globals.states[identifier] = BrowserThreadState::SHUTDOWN;

    // Wait for all pending tasks to complete.
    base::WaitableEvent flushed(base::WaitableEvent::ResetPolicy::MANUAL,
        base::WaitableEvent::InitialState::NOT_SIGNALED);
    globals.task_runners[identifier]->PostTask(
        FROM_HERE,
        base::Bind(&base::WaitableEvent::Signal, base::Unretained(&flushed)));
    {
        base::AutoUnlock auto_lock(globals.lock);
        flushed.Wait();
    }

    // Only reset the task runner after running pending tasks so that
    // BrowserThread::CurrentlyOn() works in their scope.
    globals.task_runners[identifier] = nullptr;

    // Note: it's still possible for tasks to be posted to that task runner after
    // this point (e.g. through a previously obtained ThreadTaskRunnerHandle or by
    // one of the last tasks re-posting to its ThreadTaskRunnerHandle) but the
    // BrowserThread API itself won't accept tasks. Such tasks are ultimately
    // guaranteed to run before TaskScheduler::Shutdown() returns but may break
    // the assumption in PostTaskHelper that BrowserThread::ID A > B will always
    // succeed to post to B. This is pretty much the only observable difference
    // between a redirected thread and a real one and is one we're willing to live
    // with for this experiment. TODO(gab): fix this before enabling the
    // experiment by default on trunk, http://crbug.com/653916.
}

// static
bool BrowserThreadImpl::PostTaskHelper(
    BrowserThread::ID identifier,
    const tracked_objects::Location& from_here,
    const base::Closure& task,
    base::TimeDelta delay,
    bool nestable)
{
    DCHECK_GE(identifier, 0);
    DCHECK_LT(identifier, ID_COUNT);
    // Optimization: to avoid unnecessary locks, we listed the ID enumeration in
    // order of lifetime.  So no need to lock if we know that the target thread
    // outlives current thread as that implies the current thread only ever sees
    // the target thread in its RUNNING state.
    // Note: since the array is so small, ok to loop instead of creating a map,
    // which would require a lock because std::map isn't thread safe, defeating
    // the whole purpose of this optimization.
    BrowserThread::ID current_thread = ID_COUNT;
    bool target_thread_outlives_current = GetCurrentThreadIdentifier(&current_thread) && current_thread >= identifier;

    BrowserThreadGlobals& globals = g_globals.Get();
    if (!target_thread_outlives_current)
        globals.lock.Acquire();

    const bool accepting_tasks = globals.states[identifier] == BrowserThreadState::RUNNING;
    if (accepting_tasks) {
        base::SingleThreadTaskRunner* task_runner = globals.task_runners[identifier].get();
        DCHECK(task_runner);
        if (nestable) {
            task_runner->PostDelayedTask(from_here, task, delay);
        } else {
            task_runner->PostNonNestableDelayedTask(from_here, task, delay);
        }
    }

    if (!target_thread_outlives_current)
        globals.lock.Release();

    return accepting_tasks;
}

// static
bool BrowserThread::PostBlockingPoolTask(
    const tracked_objects::Location& from_here,
    const base::Closure& task)
{
    return g_globals.Get().blocking_pool->PostWorkerTask(from_here, task);
}

// static
bool BrowserThread::PostBlockingPoolTaskAndReply(
    const tracked_objects::Location& from_here,
    const base::Closure& task,
    const base::Closure& reply)
{
    return g_globals.Get().blocking_pool->PostTaskAndReply(
        from_here, task, reply);
}

// static
bool BrowserThread::PostBlockingPoolSequencedTask(
    const std::string& sequence_token_name,
    const tracked_objects::Location& from_here,
    const base::Closure& task)
{
    return g_globals.Get().blocking_pool->PostNamedSequencedWorkerTask(
        sequence_token_name, from_here, task);
}

// static
void BrowserThread::PostAfterStartupTask(
    const tracked_objects::Location& from_here,
    const scoped_refptr<base::TaskRunner>& task_runner,
    const base::Closure& task)
{
    GetContentClient()->browser()->PostAfterStartupTask(from_here, task_runner,
        task);
}

// static
base::SequencedWorkerPool* BrowserThread::GetBlockingPool()
{
    return g_globals.Get().blocking_pool.get();
}

// static
bool BrowserThread::IsThreadInitialized(ID identifier)
{
    if (g_globals == nullptr)
        return false;

    BrowserThreadGlobals& globals = g_globals.Get();
    base::AutoLock lock(globals.lock);
    DCHECK_GE(identifier, 0);
    DCHECK_LT(identifier, ID_COUNT);
    return globals.states[identifier] == BrowserThreadState::INITIALIZED || globals.states[identifier] == BrowserThreadState::RUNNING;
}

// static
bool BrowserThread::CurrentlyOn(ID identifier)
{
    BrowserThreadGlobals& globals = g_globals.Get();
    base::AutoLock lock(globals.lock);
    DCHECK_GE(identifier, 0);
    DCHECK_LT(identifier, ID_COUNT);
    return globals.task_runners[identifier] && globals.task_runners[identifier]->RunsTasksOnCurrentThread();
}

// static
std::string BrowserThread::GetDCheckCurrentlyOnErrorMessage(ID expected)
{
    std::string actual_name = base::PlatformThread::GetName();
    if (actual_name.empty())
        actual_name = "Unknown Thread";

    std::string result = "Must be called on ";
    result += GetThreadName(expected);
    result += "; actually called on ";
    result += actual_name;
    result += ".";
    return result;
}

// static
bool BrowserThread::IsMessageLoopValid(ID identifier)
{
    if (g_globals == nullptr)
        return false;

    BrowserThreadGlobals& globals = g_globals.Get();
    base::AutoLock lock(globals.lock);
    DCHECK_GE(identifier, 0);
    DCHECK_LT(identifier, ID_COUNT);
    return globals.states[identifier] == BrowserThreadState::RUNNING;
}

// static
bool BrowserThread::PostTask(ID identifier,
    const tracked_objects::Location& from_here,
    const base::Closure& task)
{
    return BrowserThreadImpl::PostTaskHelper(
        identifier, from_here, task, base::TimeDelta(), true);
}

// static
bool BrowserThread::PostDelayedTask(ID identifier,
    const tracked_objects::Location& from_here,
    const base::Closure& task,
    base::TimeDelta delay)
{
    return BrowserThreadImpl::PostTaskHelper(
        identifier, from_here, task, delay, true);
}

// static
bool BrowserThread::PostNonNestableTask(
    ID identifier,
    const tracked_objects::Location& from_here,
    const base::Closure& task)
{
    return BrowserThreadImpl::PostTaskHelper(
        identifier, from_here, task, base::TimeDelta(), false);
}

// static
bool BrowserThread::PostNonNestableDelayedTask(
    ID identifier,
    const tracked_objects::Location& from_here,
    const base::Closure& task,
    base::TimeDelta delay)
{
    return BrowserThreadImpl::PostTaskHelper(
        identifier, from_here, task, delay, false);
}

// static
bool BrowserThread::PostTaskAndReply(
    ID identifier,
    const tracked_objects::Location& from_here,
    const base::Closure& task,
    const base::Closure& reply)
{
    return GetTaskRunnerForThread(identifier)
        ->PostTaskAndReply(from_here, task, reply);
}

// static
bool BrowserThread::GetCurrentThreadIdentifier(ID* identifier)
{
    if (g_globals == nullptr)
        return false;

    BrowserThreadGlobals& globals = g_globals.Get();
    // Profiler to track potential contention on |globals.lock|. This only does
    // real work on canary and local dev builds, so the cost of having this here
    // should be minimal.
    tracked_objects::ScopedTracker tracking_profile(FROM_HERE);
    base::AutoLock lock(globals.lock);
    for (int i = 0; i < ID_COUNT; ++i) {
        if (globals.task_runners[i] && globals.task_runners[i]->RunsTasksOnCurrentThread()) {
            *identifier = static_cast<ID>(i);
            return true;
        }
    }

    return false;
}

// static
scoped_refptr<base::SingleThreadTaskRunner>
BrowserThread::GetTaskRunnerForThread(ID identifier)
{
    return g_task_runners.Get().proxies[identifier];
}

// static
void BrowserThread::SetIOThreadDelegate(BrowserThreadDelegate* delegate)
{
    BrowserThreadGlobals& globals = g_globals.Get();
    BrowserThreadDelegateAtomicPtr old_delegate = base::subtle::NoBarrier_AtomicExchange(
        &globals.io_thread_delegate,
        reinterpret_cast<BrowserThreadDelegateAtomicPtr>(delegate));

    // This catches registration when previously registered.
    DCHECK(!delegate || !old_delegate);
}

} // namespace content
